#include <linux/errno.h>
#include <linux/fs2.h>

#define EIOCBRETRY	530	/* iocb queued, will trigger a retry */

const struct file_operations generic_ro_fops = {
	// .llseek		= generic_file_llseek,
	.read		= do_sync_read,
	.aio_read	= generic_file_aio_read,
	.mmap		= generic_file_readonly_mmap,
	// .splice_read	= generic_file_splice_read,
};

ssize_t do_sync_read(struct file *filp, char *buf, size_t len, loff_t *ppos)
{
	struct iovec iov = { .iov_base = buf, .iov_len = len };
	struct kiocb kiocb;
	ssize_t ret;

	printf("this is %s(): %d >>> *ppos = %d\r\n", __func__, __LINE__, *ppos);

	// init_sync_kiocb(&kiocb, filp);
	kiocb.ki_filp = filp;
	kiocb.ki_pos = *ppos;
	kiocb.ki_left = len;

	printf("this is %s(): %d >>> *ppos = %d, kiocb.ki_pos = %d\r\n", __func__, __LINE__, *ppos, kiocb.ki_pos);

	for (;;) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
		// ret = generic_file_aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
		if (ret != -EIOCBRETRY)
			break;
		// wait_on_retry_sync_kiocb(&kiocb);
	}

	// if (-EIOCBQUEUED == ret)
	// 	ret = wait_on_sync_kiocb(&kiocb);
	// *ppos = kiocb.ki_pos;
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return ret;
}

ssize_t vfs_read(struct file *file, char *buf, size_t count, loff_t *pos)
{
	ssize_t ret;

	printf("this is %s(): %d >>> *pos = %d\r\n", __func__, __LINE__, *pos);

	// if (!(file->f_mode & FMODE_READ))
	// 	return -EBADF;
	// if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
	// 	return -EINVAL;
	// if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
	// 	return -EFAULT;

	// ret = rw_verify_area(READ, file, pos, count);
	// if (ret >= 0) {
	// 	count = ret;
		if (file->f_op->read)
			ret = file->f_op->read(file, buf, count, pos);
		else
			ret = do_sync_read(file, buf, count, pos);
	// 	if (ret > 0) {
	// 		fsnotify_access(file->f_path.dentry);
	// 		add_rchar(current, ret);
	// 	}
	// 	inc_syscr(current);
	// }

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return ret;
}

static inline loff_t file_pos_read(struct file *file)
{
	return file->f_pos;
}

static inline void file_pos_write(struct file *file, loff_t pos)
{
	file->f_pos = pos;
}

ssize_t sys_read(int fd, char *buf, size_t count, loff_t *pos)
{
	struct file *file;
	ssize_t ret = -EBADF;
	int fput_needed;

	file = fget_light(fd, &fput_needed);
	if (file) {
		loff_t pos = file_pos_read(file);
		ret = vfs_read(file, buf, count, &pos);
		file_pos_write(file, pos);
		// fput_light(file, fput_needed);
	}

	return ret;
}

ssize_t do_sync_write(struct file *filp, const char *buf, size_t len, loff_t *ppos)
{
	struct iovec iov = { .iov_base = (void *)buf, .iov_len = len };
	struct kiocb kiocb;
	ssize_t ret;

	init_sync_kiocb(&kiocb, filp);
	kiocb.ki_pos = *ppos;
	kiocb.ki_left = len;

	for (;;) {
		ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);
		if (ret != -EIOCBRETRY)
			break;
		// wait_on_retry_sync_kiocb(&kiocb);
	}

	// if (-EIOCBQUEUED == ret)
	// 	ret = wait_on_sync_kiocb(&kiocb);
	// *ppos = kiocb.ki_pos;
	return ret;
}

ssize_t vfs_write(struct file *file, const char *buf, size_t count, loff_t *pos)
{
	ssize_t ret;

	// if (!(file->f_mode & FMODE_WRITE))
	// 	return -EBADF;
	// if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write))
	// 	return -EINVAL;
	// if (unlikely(!access_ok(VERIFY_READ, buf, count)))
	// 	return -EFAULT;

	// ret = rw_verify_area(WRITE, file, pos, count);
	// if (ret >= 0) {
		// count = ret;
		if (file->f_op->write)
			ret = file->f_op->write(file, buf, count, pos);
		else
			ret = do_sync_write(file, buf, count, pos);
		// if (ret > 0) {
		// 	fsnotify_modify(file->f_path.dentry);
		// 	add_wchar(current, ret);
		// }
		// inc_syscw(current);
	// }

	return ret;
}

ssize_t sys_write(unsigned int fd, const char *buf, size_t count)
{
	struct file *file;
	ssize_t ret = -EBADF;
	int fput_needed;

	file = fget_light(fd, &fput_needed);
	if (file) {
		loff_t pos = file_pos_read(file);
		ret = vfs_write(file, buf, count, &pos);
		file_pos_write(file, pos);
		// fput_light(file, fput_needed);
	}

	return ret;
}
